//日志管理模块：负责服务器端的日志记录，将用户注册、登录、连接、断开连接、群聊和私聊消息等信息记录到日志
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>
#include <limits.h>
#include "log.h"
#include "config.h"

// 声明一个 ServerConfig 类型的外部变量，该变量在config.h中定义，用于存储服务器配置信息。
extern ServerConfig server_config;

// 使用静态初始化方式初始化一个名为 log_mutex 的互斥锁。
// 此互斥锁用于在访问共享资源（如日志文件）时确保线程安全。
pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

/**
 *  记录日志消息到指定文件。
 * 
 * 该函数将包含时间戳、日志类型、用户名、目标用户和消息内容的日志信息
 * 写入到以日期命名的日志文件中。支持多线程环境，通过互斥锁保证线程安全。
 * 
 *  type 日志类型，如 "REGISTER", "LOGIN" 等，以 null 结尾的字符串。
 *  username 操作的用户名。
 *  message 日志消息内容。
 *  target 目标用户名称，用于私聊等场景。
 */
void log_message(const char *type, const char *username, 
                const char *message, const char *target) {
    
    time_t now = time(NULL);        // 获取当前时间的时间戳
    struct tm *t = localtime(&now); // 将时间戳转换为本地时间结构体
    char timestamp[20];             // 用于存储日期格式的时间戳

    // 将 struct tm 结构体中的时间信息格式化为字符串，生成 "YYYYMMDD" 格式的日期字符串。
    strftime(timestamp, sizeof(timestamp), "%Y%m%d", t);

    // 检查日志目录是否存在
    struct stat st = {0};   // 初始化 stat 结构体为 0，确保其成员变量的值都是 0
     printf("日志目录: %s\n", server_config.log_dir); 
    // 通过 stat 函数检查日志目录是否存在
    //若不存在则使用 mkdir 函数尝试创建，若创建失败则返回错误信息。
    //stat：用于获取文件或目录的状态信息。
    //server_config.log_dir：要检查的目录路径，从配置中获取。
    //&st：指向 stat 结构体的指针，
    //若目录存在，则返回 0，否则返回 -1。
   if (stat(server_config.log_dir, &st) == -1) {
      
    // 递归创建目录
      char cmd[PATH_MAX + 50];    // 创建一个能够容纳系统允许的最长路径的字符数组，用于存储创建目录的命令
      snprintf(cmd, sizeof(cmd), "mkdir -p %s", server_config.log_dir);
      //system 函数用于执行系统命令，这里用于创建日志目录。
       if (system(cmd) != 0) {
         perror("创建日志目录失败");
         printf("尝试创建的目录: %s\n", server_config.log_dir);

         return;
       }
   }
       
    // 构建完整日志文件路径，并检查路径长度是否超出限制
    // 创建一个能够容纳系统允许的最长路径的字符数组，用于存储完整的日志文件路径
    char filename[PATH_MAX];    

    //snprintf 函数用于将格式化的字符串复制到指定的字符数组中，检查路径长度是否超出限制。
    //filename 目标缓冲区，用于存储格式化后的日志文件路径
    //sizeof(filename) 目标缓冲区的大小，确保不会超出缓冲区的容量。
    //"%s/%s_%s.log" 格式化字符串，包含目录路径、日志文件名和日期后缀。
    //server_config.log_dir 日志目录路径，从配置中获取。
    //server_config.log_file 日志文件名，从配置中获取。
    //timestamp 日期字符串，包含年、月、日信息。
    //if语句中：
    //len<0:len < 0：snprintf 函数返回负值表示在格式化过程中出错。
    //(size_t)len >= sizeof(filename)：snprintf 函数返回的长度大于或等于 filename 数组的大小，
    //意味着如果没有缓冲区大小限制，生成的字符串长度会超出 filename 数组的容量，可能导致缓冲区溢出.
    int len = snprintf(filename, sizeof(filename), "%s/%s_%s.log", 
                 server_config.log_dir, server_config.log_file, timestamp);
    if (len <0 || (size_t)len >= sizeof(filename)) {
        perror("日志文件路径过长");
        return; //终止当前日志记录操作
    }

    // 加锁确保线程安全
    // 加互斥锁，防止多个线程同时操作日志文件
    pthread_mutex_lock(&log_mutex); //加锁
    
    // 以追加模式打开日志文件，文件不存在则创建，写入位置始终在文件末尾
    FILE *logfile = fopen(filename, "a");   //成功返回指向FILE对象的指针，失败返回NULL
    // 检查文件是否成功打开
    if (!logfile) { //等同于logfile == NULL
        perror("打开日志文件失败");
        pthread_mutex_unlock(&log_mutex);   //释放互斥锁
        return; //终止当前的日志记录操作
    }

    //用于存储格式化后的时间字符串
    char logtime[20];   
    // 将 struct tm 结构体中的时间信息格式化为字符串，生成如 "YYYY-MM-DD HH:MM:SS" 格式的时间字符串
    strftime(logtime, sizeof(logtime), "%Y-%m-%d %H:%M:%S", t);
    
     // 构建日志字符串
     //log_buffer 用于存储格式化后的日志字符串，大小为 2048 字节，足够存储 1 条日志信息。
     //offset 用于记录日志字符串的实际长度，初始值为 0。
    char log_buffer[2048];
    int offset = 0;
    
    // 格式化时间戳和日志类型信息到日志字符串中
    //snprintf 函数用于将格式化的字符串写入 log_buffer 中
    //log_buffer + offset 是写入的起始位置，offset 记录当前已写入的字符数。
    //sizeof(log_buffer) - offset 表示缓冲区剩余的可用空间，避免缓冲区溢出。
    //logtime 是格式化后的时间字符串，type 是日志类型，
    //offset += ... 用于更新已写入的字符数。
    offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, 
                      "[%s] [%s] ", logtime, type);
    
    // 添加用户名信息
    //检查 username 是否为 NULL，若不为 NULL，则将用户名信息添加到日志字符串中。
    if (username) {
        offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, 
                          "用户: %s ", username);
    }
    
    // 添加目标用户信息
    // 检查 target 是否为 NULL，若目标用户不为NULL，则添加目标用户信息到日志字符串中。
    if (target) {
        offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, 
                          "目标: %s ", target);
    }
    // 添加消息内容
    // 检查 message 是否为 NULL。
    //若消息内容不为NULL，则添加消息内容到日志字符串中。
    //若为 NULL，则添加 "消息: 无消息"。
    if (message) {
        offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, 
                          "消息: %s", message);
    } else {
        offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, 
                          "消息: 无消息");
    }
    // 添加换行符
    // 日志字符串末尾添加换行符，并写入日志文件，方便后续日志查看。
    offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, "\n");
    // 检查日志字符串是否完整
    // 检查 offset 是否超过 log_buffer 的大小，
    //若超过则说明日志字符串过长，打印错误信息并终止函数。
    
    if ((size_t)offset >= sizeof(log_buffer)) {
        perror("日志字符串过长");
        return;
    }
    // 使用fwrite一次性写入日志字符串到日志文件中。
    // log_buffer 指向日志字符串缓冲区，
    // offset 表示要写入的字节数，
    // 1 表示每个元素的大小（字节），
    // logfile 表示目标文件指针。
    fwrite(log_buffer, 1, offset, logfile);
    // 关闭日志文件
    fclose(logfile);

    // 解锁，允许其他线程操作日志文件
    pthread_mutex_unlock(&log_mutex);
}